#include <algorithm/analytical_model/algorithm/cuda/Utils.h>
#include <algorithm/analytical_model/io/csv/WriteFluxMap.h>
#include <utils/Print.h>

#include <cmath>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <memory>

#include <cuda_runtime_api.h>

using namespace solar;

namespace solar::io::csv::cuda::internal
{
    /// @brief  将FluxMap转换为字符串（csv格式）
    /// 目前仅能处理10位小数（非负）
    /// 可能在rounding上有问题
    __global__ static void writeFluxMapKernel(float* d_data, int width, int precision,
                                              char* d_output, std::int64_t output_size)
    {
        int tx = threadIdx.x;
        int ty = threadIdx.y;
        int bw = blockDim.x;
        int bh = blockDim.y;
        int x = blockIdx.x * bw + tx;
        int y = blockIdx.y * bh + ty;
        if (x > output_size)
        {
            return;
        }
        float num = d_data[x];
        char tmp_result[10] = {'0', '0', '0', '0', '0', '0', '0', '0', '0', '0'};
        float integer;
        auto float_part = std::modf(num, &integer);
        // auto num_copy = num;
        int integer_count = 0;
        int float_count = 0;
        while (integer > 1)
        {
            int tmp = std::fmod(integer, 10);
            tmp_result[integer_count] = '0' + tmp;
            integer /= 10.0F;
            ++integer_count;
        }
        while (precision - integer_count - float_count > 0)
        {
            float_part = float_part * 10;
            int tmp = static_cast<int>(std::floor(float_part)) % 10;
            tmp_result[integer_count + 1 + float_count] = '0' + tmp;
            float_count++;
        }
        char* result = &d_output[(precision + 2) * x];
        if (num < 1)
        {
            result[0] = '0';
            result[1] = '.';
            integer_count = 1;
            float_count = precision - integer_count;
        }
        for (int i = integer_count - 1, j = 0; i >= 0; i--, j++)
        {
            result[j] = tmp_result[i];
        }
        result[integer_count] = '.';
        for (int i = 0; i < float_count; i++)
        {
            result[integer_count + i + 1] = tmp_result[integer_count + i + 1];
        }
        if ((x + 1) % width == 0)
        {
            result[precision + 1] = '\n';
        }
        else
        {
            result[precision + 1] = ',';
        }
    }
} // namespace solar::io::csv::cuda::internal

auto solar::io::csv::cuda::writeFluxMap(const std::string_view& file_name, float* d_data,
                                        int height, int width, int precision) -> void
{
    char* d_output;
    std::int64_t output_length = static_cast<std::int64_t>(height) * width * (precision + 2);
    cudaMalloc(&d_output, sizeof(char) * output_length);
    solar::io::csv::cuda::internal::writeFluxMapKernel<<<(height * width + 32 - 1) / 32, 32>>>(
        d_data, width, precision, d_output, height * width);
    CHECK_CUDA_ERRORS(cudaDeviceSynchronize());
    auto output_ptr = std::make_shared<char[]>(output_length);
    char* output = output_ptr.get();
    solar::cuda::gpu2cpu(output, d_output, output_length);
    output[output_length - 1] = '\0';
    std::ofstream fout(file_name.data());
    fout.write(output, output_length);
    fout.flush();
    fout.close();
    cudaFree(d_output);
}
